//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;

namespace Microsoft.Research.DataLayer
{
    public class ConnectionManager : ConnectionManagerBase
    {
        // Project specific settings -- START ----------------------
        private const string PROJECT_NAME = "Trident";
        // Project specific settings -- END ------------------------

        private const string APPLICATION_PREFIX = "Application";

        public enum Agent
        {
            UserTrayExecutionService,
            ExecutionService,
            WebService,
        }

        private enum AgentType
        {
            SystemAgent,
            UserAgent,
        }

        private ConnectionManager(CancelBehavior cancelBehavior)
            : base((ConnectionManagerBase.CancelBehavior)cancelBehavior)
        {
        }

        private ConnectionManager(CancelBehavior cancelBehavior, string alternateStore)
            : base((ConnectionManagerBase.CancelBehavior)cancelBehavior, alternateStore)
        {
        }

        private ConnectionManager(CancelBehavior cancelBehavior, ConfigScope scope)
            : base((ConnectionManagerBase.CancelBehavior)cancelBehavior, scope)
        {
        }

        /// <summary>
        /// Support for passing specific provider assembly path
        /// </summary>
        /// <param name="cancelBehavior"></param>
        /// <param name="alternateStore"></param>
        private ConnectionManager(CancelBehavior cancelBehavior, string alternateStore, string alternateProviderPath)
            : base((ConnectionManagerBase.CancelBehavior)cancelBehavior, alternateStore)
        {
        }

        /// <summary>
        /// Creates a new connection manager object, using default connection store
        /// </summary>
        /// <param name="cancelBehavior"></param>
        /// <returns></returns>
        public static ConnectionManager Create(CancelBehavior cancelBehavior)
        {
            return new ConnectionManager(cancelBehavior);
        }

        /// <summary>
        /// Creates a new connection manager object, using default connection store
        /// </summary>
        /// <param name="cancelBehavior"></param>
        /// <returns></returns>
        public static ConnectionManager Create(CancelBehavior cancelBehavior, ConfigScope scope)
        {
            return new ConnectionManager(cancelBehavior, scope);
        }

        /// <summary>
        /// Creates a connection and separate them based on the agent being
        /// referenced. That allows the co-existence of many different agents
        /// and have different connection managers, without adding the file
        /// names or paths to the application code.
        /// </summary>
        /// <param name="agent"></param>
        /// <returns></returns>
        public static ConnectionManager CreateForAgent(CancelBehavior cancelBehavior, Agent agent)
        {
            AgentType agentType;

            switch (agent)
            {
                case Agent.ExecutionService:
                case Agent.WebService:
                    agentType = AgentType.SystemAgent;
                    break;
                case Agent.UserTrayExecutionService:
                    agentType = AgentType.UserAgent;
                    break;
                default:
                    throw new NotImplementedException("Unknown agent type");
            }

            string configPath = ConnectionManager.BuildPath(agent, agentType);
            return new ConnectionManager(cancelBehavior, configPath);
        }

        /// <summary>
        /// Creates a new connection manager object, using specific file as store
        /// </summary>
        /// <param name="cancelBehavior"></param>
        /// <param name="alternatePath"></param>
        /// <returns></returns>
        public static ConnectionManager CreateCustom(CancelBehavior cancelBehavior, string alternatePath)
        {
            return new ConnectionManager(cancelBehavior, alternatePath);
        }

        /// <summary>
        /// Creates a new connection manager object, using specific file as store, and specific provider assembly path
        /// </summary>
        /// <param name="cancelBehavior"></param>
        /// <param name="alternatePath"></param>
        /// <param name="alternateProviderPath"></param>
        /// <returns></returns>
        public static ConnectionManager CreateCustom(CancelBehavior cancelBehavior, string alternatePath, string alternateProviderPath)
        {
            return new ConnectionManager(cancelBehavior, alternatePath, alternateProviderPath);
        }

        private const string ConfigFileMask = @"{0}\Research\" + PROJECT_NAME + @"\ConnStore-{1}.configSources";

        private static string BuildPath(Agent agent, AgentType type)
        {
            return BuildPath(agent.ToString(), type);
        }

        private static string BuildPath(string app, AgentType type)
        {
            Environment.SpecialFolder folder;

            switch (type)
            {
                case AgentType.SystemAgent:
                    folder = Environment.SpecialFolder.CommonApplicationData;
                    break;
                case AgentType.UserAgent:
                    folder = Environment.SpecialFolder.ApplicationData;
                    break;
                default:
                    throw new NotImplementedException("Unknown agent type");
            }

            string appFolder = System.Environment.GetFolderPath(folder);
            return string.Format(ConfigFileMask, appFolder, app);
        }

        private static string BuildPath(string app, ConfigScope scope)
        {
            Environment.SpecialFolder folder;

            switch (scope)
            {
                case ConfigScope.Normal:
                    folder = Environment.SpecialFolder.ApplicationData;
                    break;
                case ConfigScope.Template:
                    folder = Environment.SpecialFolder.CommonApplicationData;
                    break;
                default:
                    throw new NotImplementedException("Unknown scope");
            }

            string appFolder = System.Environment.GetFolderPath(folder);
            return string.Format(ConfigFileMask, appFolder, app);
        }

        protected override ConnectionManagerBase CreateNewInstance(CancelBehavior cancelBehavior, ConfigScope scope)
        {
            return ConnectionManager.Create(cancelBehavior, scope);
        }

        protected override string DefaultConfigFile
        {
            get
            {
                return BuildPath(APPLICATION_PREFIX, scope);
            }
        }

        protected override string ConfigFileName
        {
            get
            {
                if (HasAlternateStorage)
                    return alternateStorage;

                return DefaultConfigFile;
            }
        }

        protected override string ConfigFile
        {
            get
            {
                if (HasAlternateStorage)
                {
                    if (!File.Exists(alternateStorage))
                        throw new ConnectionStoreFileNotFound(alternateStorage);

                    return alternateStorage;
                }

                string defaultConfigFile = DefaultConfigFile;

                if (!File.Exists(defaultConfigFile))
                {
                    // If we are using the Normal scope and there is a template,
                    // create a copy of the template
                    if (scope == ConfigScope.Normal)
                    {
                        string template = BuildPath(APPLICATION_PREFIX, ConfigScope.Template);
                        if (File.Exists(template))
                        {
                            string fileDir = Path.GetDirectoryName(defaultConfigFile);
                            if (!Directory.Exists(fileDir))
                            {
                                // Try to create the folder
                                Directory.CreateDirectory(fileDir);
                            }

                            File.Copy(template, defaultConfigFile);
                        }
                        else
                        {
                            throw new ConnectionStoreFileNotFound(defaultConfigFile);
                        }
                    }
                    else
                    {
                        throw new ConnectionStoreFileNotFound(defaultConfigFile);
                    }
                }

                return defaultConfigFile;
            }
        }
    }
}
